package it.yuzuojian.com.util;

import it.yuzuojian.com.annotation.mvc.RequestMapping;
import it.yuzuojian.com.annotation.mvc.RequestParam;
import it.yuzuojian.com.annotation.mvc.ResponseBody;
import it.yuzuojian.com.handle.impl.mvc.SimpleDateHandle;
import it.yuzuojian.com.handle.mvc.DateHandle;
import it.yuzuojian.com.pojo.BeanDefinition;
import it.yuzuojian.com.pojo.BeanFactory;
import it.yuzuojian.com.pojo.MethodAndObject;
import it.yuzuojian.com.pojo.mvc.ModelAndView;
import it.yuzuojian.com.pojo.mvc.ModelMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class DispatcherServletUtil {
    private ApplicationContextUtil applicationContextUtil = new ApplicationContextUtil();
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = BeanFactory.getBeanDefinitions();

    public void doInit(ConcurrentHashMap<String, MethodAndObject> methods) {
        beanDefinitions.forEach((beanName, beanDefinition) -> {
            Class clazz = beanDefinition.getClazz();
            String head = "";
            if (beanDefinition.isController()) {
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping declaredAnnotation = (RequestMapping) clazz.getDeclaredAnnotation(RequestMapping.class);
                    head = declaredAnnotation.value();
                }
                try {
                    Object bean = getBean(beanDefinition.getBeanName());
                    Class<?> aClass = bean.getClass();
                    Method[] declaredMethods = clazz.getDeclaredMethods();
                    for (Method declaredMethod : declaredMethods) {
                        if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping declaredAnnotation = (RequestMapping) declaredMethod.getDeclaredAnnotation(RequestMapping.class);
                            String body = declaredAnnotation.value();
                            MethodAndObject methodAndObject = new MethodAndObject();
                            methodAndObject.setMethod(aClass.getDeclaredMethod(declaredMethod.getName(), declaredMethod.getParameterTypes()));
                            methodAndObject.setObject(bean);
                            if(declaredMethod.isAnnotationPresent(ResponseBody.class)){
                                methodAndObject.setReturnJson(true);
                            }else {
                                methodAndObject.setReturnJson(false);
                            }
                            methods.put(head + body, methodAndObject);
                        }
                    }
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public Object getBean(String beanName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        ConcurrentHashMap<String, BeanDefinition> beanDefinitions = BeanFactory.getBeanDefinitions();
        ConcurrentHashMap<String, Object> originalObjects = BeanFactory.getOriginalObjects();
        ConcurrentHashMap<String, Object> singletonObjects = BeanFactory.getSingletonObjects();
        BeanDefinition beanDefinition = beanDefinitions.get(beanName);
        return applicationContextUtil.getBean(beanDefinition, originalObjects, singletonObjects);
    }

    public Object[] getParams(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView, Method method) throws ParseException, InstantiationException, IllegalAccessException {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Parameter[] parameters = method.getParameters();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (method.getParameterCount() == 0) {
            return null;
        }
        Object[] params = new Object[method.getParameterCount()];
        int i = 0;
        for (Class<?> parameterType : parameterTypes) {
            if (parameterType == HttpServletRequest.class) {
                params[i] = request;
            } else if (parameterType == HttpServletResponse.class) {
                params[i] = response;
            } else if (parameterType == ModelAndView.class) {
                params[i] = modelAndView;
            } else {
                doTypeChange(parameterType, request, params, parameters, i,parameterAnnotations);
            }
            i++;
        }
        return params;
    }

    public void doTypeChange(Class parameterType, HttpServletRequest request, Object[] params,
                             Parameter[] parameters, int i,Annotation[][] parameterAnnotations) throws ParseException, InstantiationException, IllegalAccessException {
        String parameter1 = request.getParameter(parameters[i].getName());
        String value = null;
        Annotation[] parameterAnnotation = parameterAnnotations[i];
        for (Annotation annotation : parameterAnnotation) {
            if(annotation instanceof RequestParam){
                 value = ((RequestParam) annotation).value();
                if(value != null && !"".equals(value) && !" ".equals(value)){
                    parameter1=request.getParameter(value);
                }
            }
        }
        if (String.class == parameterType) {
            params[i] = String.valueOf(parameter1);
        } else if (parameterType.isArray()) {
            String[] parameter2 = request.getParameterValues(parameters[i].getName());
            if(value != null && !"".equals(value) && !" ".equals(value)){
                parameter2=request.getParameterValues(value);
            }
            params[i] = parameter2;
        } else if (Integer.class == parameterType) {
            params[i] = Integer.parseInt(parameter1);
        } else if (Date.class == parameterType) {
            List<DateHandle> dateHandles = BeanFactory.getDateHandles();
            if (dateHandles.size() == 0) {
                SimpleDateHandle simpleDateHandle = new SimpleDateHandle();
                params[i] = simpleDateHandle.format(parameter1);
            } else {
                DateHandle dateHandle = dateHandles.get(0);
                params[i] = dateHandle.format(parameter1);
            }
        } else if (Double.class == parameterType) {
            params[i] = Double.parseDouble(parameter1);
        } else {
            doTypeChangeForObject(request,parameterType,params,i);
        }
    }

    public ModelAndView setModelAndViewParams(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        ModelMap model = new ModelMap();
        Map<String, String[]> parameterMap = request.getParameterMap();
        parameterMap.forEach((key, value) -> {
            if (value.length == 1) {
                model.addAttribute(key, value[0]);
            } else {
                model.addAttribute(key, value);
            }
        });
        modelAndView.setModel(model);
        return modelAndView;
    }

    /**
     * 目前针对自定义类型参数转换，只支持类只有普通类型对象
     * 主要问题： 1、如果A类里面的B类的属性名和A类一样，如何处理
     *          2、目前只支持数组类型，List啥的不支持,数组由于反射获取不到长度，改为前端传入参数的长度
     * @param request
     * @param parameterType
     * @param params
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public void doTypeChangeForObject(HttpServletRequest request , Class parameterType,Object[] params,int i) throws InstantiationException, IllegalAccessException {
        Object object = parameterType.newInstance();
        Map<String, String[]> parameterMap = request.getParameterMap();
        Field[] declaredFields = parameterType.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            parameterMap.forEach((key, value) -> {
                if (key.equals(declaredField.getName())) {
                    Class<?> type = declaredField.getType();
                    if(type.isArray()){
                        try {
                            Class<?> componentType = type.getComponentType();
                            if (Integer.class == componentType || int.class == componentType) {
                                Integer[] integers = new Integer[value.length];
                                for (int i1 = 0; i1 < value.length; i1++) {
                                    integers[i1]=Integer.parseInt(value[i1]);
                                }
                                declaredField.set(object, integers);
                            } else if (String.class == componentType) {
                                String[] strings = new String[value.length];
                                for (int i1 = 0; i1 < value.length; i1++) {
                                    strings[i1]=String.valueOf(value[i1]);
                                }
                                declaredField.set(object, strings);
                            } else if (Date.class == componentType) {
                                List<DateHandle> dateHandles = BeanFactory.getDateHandles();
                                if (dateHandles.size() == 0) {
                                    SimpleDateHandle simpleDateHandle = new SimpleDateHandle();
                                    Date[] dates = new Date[value.length];
                                    for (int i1 = 0; i1 < value.length; i1++) {
                                        dates[i1]=simpleDateHandle.format(value[i1]);
                                    }
                                    declaredField.set(object, dates);
                                }else {
                                    DateHandle dateHandle = dateHandles.get(0);
                                    Date[] dates = new Date[value.length];
                                    for (int i1 = 0; i1 < value.length; i1++) {
                                        dates[i1]=dateHandle.format(value[i1]);
                                    }
                                    declaredField.set(object, dates);
                                }
                            }
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }else {
                        try {
                            if (Integer.class == declaredField.getType() || int.class == declaredField.getType()) {
                                declaredField.set(object, Integer.parseInt(value[0]));
                            } else if (String.class == declaredField.getType()) {
                                declaredField.set(object, String.valueOf(value[0]));
                            } else if (Date.class == declaredField.getType()) {
                                List<DateHandle> dateHandles = BeanFactory.getDateHandles();
                                if (dateHandles.size() == 0) {
                                    SimpleDateHandle simpleDateHandle = new SimpleDateHandle();
                                    declaredField.set(object, simpleDateHandle.format(value[0]));
                                }else {
                                    DateHandle dateHandle = dateHandles.get(0);
                                    declaredField.set(object, dateHandle.format(value[0]));
                                }
                            }
                        } catch (IllegalAccessException | ParseException e) {
                            throw new RuntimeException("数据转换异常");
                        }
                    }
                }

            });
        }
        params[i] = object;
    }

}
